library("tidyverse")
library("ggplot2")
library(plyr)
library("mco")
run_1 = read_csv('/Users/b1017579/Documents/PhD/Projects/10. ELECSIM/run/validation-optimisation/data/run_2.csv')
Missing column names filled in: 'X1' [1]Parsed with column specification:
cols(
  X1 = col_double(),
  id = col_double(),
  run_number = col_double(),
  time_taken = col_double(),
  timestamp_start = col_double(),
  timestamp_end = col_double(),
  reward = col_double(),
  individual_m = col_double(),
  individual_c = col_double(),
  coal = col_double(),
  nuclear = col_double(),
  ccgt = col_double(),
  wind = col_double(),
  solar = col_double()
)
tail(run_1)
ggplot(filter(run_1), aes(y=individual_m, x=individual_c)) + geom_hex(bins=10)

p = ggplot(run_1, aes(y=individual_m, x=individual_c, color=reward), size=10) + facet_wrap(~run_number) + geom_point() + ggtitle("Scatter plot of input parameters and reward")
p$labels$fill <- "Absolute \nPercentage \nError"
print(p)

p = ggplot(run_1, aes(y=individual_m, x=individual_c, color=reward), size=10) + facet_wrap(~run_number) + geom_jitter() + ggtitle("Scatter (jitter) plot of input parameters and reward")
p$labels$fill <- "Absolute \nPercentage \nError"
print(p)

p = ggplot(run_1, aes(y=individual_m, x=individual_c, color=time_taken), size=10) + facet_wrap(~run_number) + geom_jitter() + ggtitle("Scatter plot of input parameters and time taken")
p$labels$fill <- "Time \nTaken"
print(p)

p=ggplot(run_1, aes(y=individual_m, x=individual_c)) + stat_summary_hex(aes(z = reward), bins=10) + facet_wrap(~ run_number) + scale_x_continuous(breaks = round(seq(min(run_1$individual_c), max(run_1$individual_c), by = 25),1)) + ggtitle("Hexagonal heatmap of input parameters and Absolute Percentage Error")
p$labels$fill <- "Absolute \nPercentage \nError"
print(p)
ggsave("~/Desktop/genetic_algorithm_progression.png")
Saving 6.89 x 4.26 in image

run_1 %>% group_by(run_number) %>% summarise(avg_reward = mean(reward)) %>% ggplot()+ geom_smooth(data=run_1, aes(x=run_number, reward))+geom_line(aes(x=run_number, y=avg_reward))+ggtitle("Average reward vs Population Number") 

# accurate_area = filter(run_1, individual_c<-4.8, individual_c>-50, individual_m <0.003, individual_m > 0.0023)
# accurate_area = filter(run_1, reward < 0.2)
# accurate_area = filter(run_1, run_number==11)
accurate_area = filter(run_1, run_number == 16)
accurate_area
 ggplot(data=accurate_area, aes(x=individual_c, y=individual_m, color=reward))+geom_jitter()+geom_point(color="red", alpha=0.5) + ggtitle("Scatter and Jitterplot of Reward against Input Parameters for \nLast Population of GA")

accurate_area_long = gather(accurate_area, "key", "value", "coal", "ccgt", "wind", "nuclear", "solar")
accurate_area_long_perc = accurate_area_long %>% group_by(id) %>% mutate(value_perc = value/sum(value))
params_more_than_10 = run_1 %>% group_by(individual_c, individual_m) %>% mutate(number = n()) %>% ungroup() %>% filter(number> 10) %>% mutate_if(is.numeric, round, 6) 
params_more_than_10 %>% ggplot(aes(as.factor(individual_c), reward)) +geom_violin()+facet_wrap(~individual_m)+ theme(axis.text.x = element_text(angle = 90, hjust = 1)) +geom_jitter(position=position_jitter(0.2), alpha=0.05) + ggtitle("Violin plot of input parameters with more than 10 simulations \nagainst distribution of reward")

accurate_area %>% mutate_if(is.numeric, round, 6) %>% ggplot(aes(as.factor(individual_c), reward)) +geom_violin()+facet_wrap(~individual_m)+ theme(axis.text.x = element_text(angle = 90, hjust = 1)) +geom_jitter(position=position_jitter(0.2))+ ggtitle("Violin plot of input parameters with more than 10 simulations of last population of GA\nagainst distribution of reward")

actual_mix = read_csv('/Users/b1017579/Documents/PhD/Projects/10. ELECSIM/elecsim/data/processed/electricity_mix/energy_mix_historical.csv')
Missing column names filled in: 'X1' [1]Parsed with column specification:
cols(
  X1 = col_double(),
  year = col_double(),
  variable = col_character(),
  value = col_double()
)
actual_mix_2018 = filter(actual_mix, year==2018)
actual_mix_2018$type = "actual"
actual_mix_2018
actual_mix_2018_reduced = filter(actual_mix_2018, variable %in% c("ccgt", 'wind', 'nuclear', 'solar', 'coal'))
actual_mix_2018_reduced = actual_mix_2018_reduced %>% mutate(value_perc = value/sum(value))
actual_mix_2018_reduced = dplyr::rename(actual_mix_2018_reduced, key=variable)
head(actual_mix_2018_reduced)
accurate_area_long_perc$type = 'predicted'
accurate_area_long_perc
comparison = rbind(select(ungroup(accurate_area_long_perc), "key", "type", "value", 'value_perc'), select(actual_mix_2018_reduced, -X1, -year))
ggplot(comparison, aes(x=key, y=value_perc, fill=type)) +
  stat_summary(geom = "bar", fun.y = mean, position = "dodge2")+ stat_summary(geom = "errorbar", fun.data = mean_se, position = "dodge2") + ylab("Energy Mix (%)") + ggtitle("Combination of all runs in last genetic algorithm run")
ggsave('~/Desktop/average_error_of_best_params.png')
Saving 6.89 x 4.26 in image

best_result = run_1 %>% group_by("id") %>% filter(reward==min(reward)) %>% gather("key", "value", "coal", "ccgt", "wind", "nuclear", "solar") %>% group_by(id) %>% mutate(value_perc = value/sum(value)) %>% mutate(type="predicted")
comparison_best = rbind(select(ungroup(best_result), "key", "type", "value", 'value_perc'), select(actual_mix_2018_reduced, -X1, -year))
ggplot(comparison_best, aes(x=key, y=value_perc, fill=type)) + geom_col(position = "dodge2") + ylab("Energy Mix (%)") + ggtitle("Best single run")

pdc_example = read_csv('/Users/b1017579/Documents/PhD/Projects/10. ELECSIM/notebooks/validation-optimisation/data/price_demand_curve_example.csv')
Missing column names filled in: 'X1' [1]Parsed with column specification:
cols(
  X1 = col_double(),
  accepted_price = col_double(),
  segment_demand = col_double(),
  segment_hour = col_double()
)
demand_range = data.frame(x=seq(from=min(pdc_example$segment_demand),max(pdc_example$segment_demand),length.out=500))
get_line = function(c, m){
    y = m * demand_range + c
    return(y)
}
get_x = function(){
    return(demand_range)
}
# lines = accurate_area %>% group_by(id) %>% apply(get_line(.))
lines = ddply(accurate_area, .(id), transform, y=get_line(individual_c, individual_m), x=get_x())
ggplot() + geom_line(data=lines, aes(x=x.1, y=x, group=id, color=reward)) + stat_smooth(data=pdc_example, aes(x=segment_demand, y=accepted_price), method="lm", col="red") + geom_point(data=pdc_example, aes(x=segment_demand, y=accepted_price)) + xlab("Demand (MW)") + ylab("Accepted Price") + ggtitle("Last genetic algorithm run price curves between 2023-2028 \ncompared to price in 2018")

best_75_percentile = accurate_area %>% group_by(individual_c, individual_m) %>% summarise(number=n(), quantiles = list(enframe(quantile(reward, probs=c(0.25,0.5,0.75))))) %>% unnest %>% ungroup() %>% filter(number>10) %>% group_by(name) %>%filter(rank(value, ties.method="first")==1)
p = ddply(best_75_percentile, .(name), transform, y=get_line(individual_c, individual_m), x=get_x())  %>% ggplot() + geom_line(aes(x=x.1, y=x, color=name))+ stat_smooth(data=pdc_example, aes(x=segment_demand, y=accepted_price), method="lm", col="yellow") + geom_point(data=pdc_example, aes(x=segment_demand, y=accepted_price)) + xlab("Demand (MW)") + ylab("Accepted Price") + ggtitle("Price curve of best runs at different percentiles of last genetic \nalgorithm population")
p$labels$fill <- "Percentiles"
print(p)

best_percentiles_comparison = inner_join(accurate_area, best_75_percentile, by=c('individual_c', 'individual_m')) %>% gather("key", "value", "coal", "ccgt", "wind", "nuclear", "solar") %>% group_by(id) %>% mutate(value_perc = value/sum(value)) %>% mutate(type=name)
comparison_percentiles = rbind(select(ungroup(best_percentiles_comparison), "key", "type", "value", 'value_perc'), select(actual_mix_2018_reduced, -X1, -year))
comparison_percentiles %>% ggplot(aes(x=key, y=value_perc, fill=type)) + stat_summary(geom = "bar", fun.y = mean, position = "dodge2")+ stat_summary(geom = "errorbar", fun.data = mean_se, position = "dodge2") + ggtitle("Comparison of energy mix of final genetic algorithm population \nat different quantile levels")

rbind(filter(select(ungroup(best_percentiles_comparison), "key", "type", "value", 'value_perc'),type=="50%"), select(actual_mix_2018_reduced, -X1, -year)) %>% ggplot(aes(x=key, y=value_perc, fill=type)) + stat_summary(geom = "bar", fun.y = mean, position = "dodge2")+ stat_summary(geom = "errorbar", fun.data = mean_se, position = "dodge2") + ggtitle("Best parameter combinations from final genetic algorithm run")

BEST PARAMETER COMBINATION FROM ALL RUNS

best_75_percentile_all_runs = run_1 %>% group_by(individual_c, individual_m) %>% summarise(number=n(), quantiles = list(enframe(quantile(reward, probs=c(0.25,0.5,0.75, 0.9, 1.0))))) %>% unnest %>% ungroup() %>% filter(number>10) %>% group_by(name) %>%filter(rank(value, ties.method="first")==1)
p = ddply(best_75_percentile_all_runs, .(name), transform, y=get_line(individual_c, individual_m), x=get_x())  %>% ggplot() + geom_line(aes(x=x.1, y=x, color=name))+ stat_smooth(data=pdc_example, aes(x=segment_demand, y=accepted_price), method="lm", col="yellow") + geom_point(data=pdc_example, aes(x=segment_demand, y=accepted_price)) + xlab("Demand (MW)") + ylab("Accepted Price") + ggtitle("Percentiles from all runs")
p$labels$fill <- "Percentiles"
print(p)

best_percentiles_comparison = inner_join(run_1, best_75_percentile_all_runs, by=c('individual_c', 'individual_m')) %>% gather("key", "value", "coal", "ccgt", "wind", "nuclear", "solar") %>% group_by(id) %>% mutate(value_perc = value/sum(value)) %>% mutate(type=name)
comparison_percentiles = rbind(select(ungroup(best_percentiles_comparison), "key", "type", "value", 'value_perc'), select(actual_mix_2018_reduced, -X1, -year))
comparison_percentiles %>% ggplot(aes(x=key, y=value_perc, fill=type)) + stat_summary(geom = "bar", fun.y = mean, position = "dodge2")+ stat_summary(geom = "errorbar", fun.data = mean_se, position = "dodge2")

rbind(filter(select(ungroup(best_percentiles_comparison), "key", "type", "value", 'value_perc'),type=="75%" | type=="90%"), select(actual_mix_2018_reduced, -X1, -year)) %>% ggplot(aes(x=key, y=value_perc, fill=type)) + stat_summary(geom = "bar", fun.y = mean, position = "dodge2")+ stat_summary(geom = "errorbar", fun.data = mean_se, position = "dodge2") + ggtitle("Best parameter combinations from all genetic algorithm runs")

Calculation of best quantile to select combination of parameters for lowest reward

best_params = params_more_than_10 %>% group_by(individual_c, individual_m) %>% gather("key", "value", "coal", "ccgt", "wind", "nuclear", "solar") %>% group_by(id) %>% mutate(value_perc = value/sum(value)) %>% group_by(id, key) %>% inner_join(actual_mix_2018_reduced, by="key") %>% mutate(diff_perc = abs(value_perc.x-value_perc.y)) %>% group_by(id) %>% summarise(mean_diff_perc = mean(diff_perc), individual_c = mean(individual_c), individual_m = mean(individual_m)) %>% filter(rank(mean_diff_perc, ties.method="first")==1)
best_params_comparison = filter(run_1,near(x=individual_c, y=best_params$individual_c[1], tol=0.00001),near(x=individual_m, y=best_params$individual_m[1], tol=0.00001)) %>% gather("key", "value", "coal", "ccgt", "wind", "nuclear", "solar") %>% group_by(id) %>% mutate(value_perc = value/sum(value)) %>% mutate(type="predicted")
comparison_best_params = rbind(select(ungroup(best_params_comparison), "key", "type", "value", 'value_perc'), select(actual_mix_2018_reduced, -X1, -year))
comparison_best_params %>% ggplot(aes(x=key, y=value_perc, fill=type)) + stat_summary(geom = "bar", fun.y = mean, position = "dodge2")+ stat_summary(geom = "errorbar", fun.data = mean_se, position = "dodge2") + ggtitle("Comparison of predicted vs actual for best parameter set over 10 from all runs")

## Getting MAPE for best param
predicted_means_best_param = filter(run_1,near(x=individual_c, y=best_params$individual_c[1], tol=0.00001),near(x=individual_m, y=best_params$individual_m[1], tol=0.00001)) %>% gather("key", "value", "coal", "ccgt", "wind", "nuclear", "solar") %>% group_by(id) %>% mutate(value_perc = value/sum(value)) %>% mutate(type="predicted") %>% group_by(individual_c, individual_m, key) %>% summarise(predicted_perc = mean(value_perc)) 
print(paste("MAPE = ", MAPE(predicted_means_best_param$predicted_perc, actual_mix_2018_reduced$value_perc)))
[1] "MAPE =  1.86812340451084"
p = best_params %>% transform(y=get_line(individual_c, individual_m), x=get_x())  %>% ggplot() + geom_line(aes(x=x.1, y=x))+ stat_smooth(data=pdc_example, aes(x=segment_demand, y=accepted_price), method="lm", col="yellow") + geom_point(data=pdc_example, aes(x=segment_demand, y=accepted_price)) + xlab("Demand (MW)") + ylab("Accepted Price") + ggtitle("Best paramater combination price curve")
p$labels$fill <- "Percentiles"
print(p)

Calculation of best parameters with lowest variance and error

# best_params = params_more_than_10 %>% group_by(individual_c, individual_m) %>% gather("key", "value", "coal", "ccgt", "wind", "nuclear", "solar") %>% group_by(id) %>% mutate(value_perc = value/sum(value)) %>% group_by(id, key) %>% inner_join(actual_mix_2018_reduced, by="key") %>% mutate(diff_perc = abs(value_perc.x-value_perc.y)) %>% group_by(id) %>% summarise(mean_diff_perc = mean(diff_perc), individual_c = mean(individual_c), individual_m = mean(individual_m)) %>% filter(rank(mean_diff_perc, ties.method="first")==1)
# calculate_mean_error = function(df){
#     
#     return(gather(df, "key", "value", "coal", "ccgt", "wind", "nuclear", "solar") %>% inner_join(actual_mix_2018_reduced, by='key'))
# }
# params_more_than_10 %>% group_by(individual_c, individual_m) %>% do(., calculate_mean_error)
# ddply(params_more_than_10, .(individual_c, individual_m), calculate_mean_error)
params_sd_mean_reward = params_more_than_10 %>% group_by(individual_c, individual_m) %>% gather("key", "value", "coal", "ccgt", "wind", "nuclear", "solar") %>% group_by(id) %>% mutate(value_perc = value/sum(value)) %>% group_by(id, key) %>% inner_join(actual_mix_2018_reduced, by="key") %>% mutate(diff_perc = abs(value_perc.x-value_perc.y)) %>% group_by(id) %>% summarise(diff_sd = sd(diff_perc), mean_diff_perc = mean(diff_perc), individual_c = mean(individual_c), individual_m = mean(individual_m)) %>% group_by(individual_c, individual_m) %>% summarise(diff_sd = sd(diff_sd), mean_diff_perc = mean(mean_diff_perc))
params_sd_mean_reward_ordered = arrange(params_sd_mean_reward, diff_sd, mean_diff_perc)
frontier = get_frontier(as.data.frame(params_sd_mean_reward_ordered), diff_sd, mean_diff_perc, decreasing = FALSE, quadrant = "bottom.left")
params_sd_mean_reward %>% ggplot() + geom_point(aes(diff_sd, mean_diff_perc)) + geom_line(data=frontier, aes(diff_sd, mean_diff_perc), color="red") + ggtitle("Pareto frontier of standard deviation versus mean for simulations \nof more than 10 runs")

params_more_than_10_perc = params_more_than_10 %>% group_by(individual_c, individual_m) %>% gather("key", "value", "coal", "ccgt", "wind", "nuclear", "solar") %>% group_by(id) %>% mutate(value_perc = value/sum(value))
frontier_full_data = frontier %>% inner_join(params_sd_mean_reward_ordered, by=c('diff_sd', 'mean_diff_perc')) %>% inner_join(params_more_than_10_perc, by=c("individual_c", "individual_m")) %>% mutate(type=paste(mean_diff_perc,diff_sd))
rbind(filter(select(frontier_full_data, "key", "type", "value", 'value_perc')), select(actual_mix_2018_reduced, -X1, -year)) %>% ggplot(aes(x=key, y=value_perc, fill=type)) + stat_summary(geom = "bar", fun.y = mean, position = "dodge2")+ stat_summary(geom = "errorbar", fun.data = mean_se, position = "dodge2") + ggtitle("Best parameter combinations from all genetic algorithm runs")

NA
best_one = select(filter(frontier_full_data, mean_diff_perc<0.067), "key", "type", "value", 'value_perc')
best_one$type = "Simulated"
actual_mix_2018_reduced$type = "Actual"
rbind(best_one, select(actual_mix_2018_reduced, -X1, -year)) %>% ggplot(aes(x=key, y=value_perc, fill=type)) + stat_summary(geom = "bar", fun.y = mean, position = "dodge2")+ stat_summary(geom = "errorbar", fun.data = mean_se, position = "dodge2") + theme_classic() +theme(text = element_text(size=18))+ xlab("") + ylab("Electricity Mix (%)") +scale_fill_brewer(palette = 'Set2')
ggsave("~/Documents/PhD/Projects/10. ELECSIM/notebooks/validation-optimisation/figures/introduction/best_run.pdf")
Saving 6.89 x 4.26 in image

RMSE(actuals,predicted)
[1] 0.04512687
p=ggplot(run_1, aes(y=individual_m, x=individual_c)) + stat_summary_hex(aes(z = time_taken), bins=10) + facet_wrap(~ run_number) + scale_x_continuous(breaks = round(seq(min(run_1$individual_c), max(run_1$individual_c), by = 25),1)) + ggtitle("Hexagonal heatmap of input parameters against time taken with respect to population number")
p$labels$fill <- "Time \n Taken"
print(p)

ggsave('~/Desktop/time-taken-parameters.png')
run_1 %>% arrange(desc(run_number)) %>% ggplot(alpha=0.1, aes(y=individual_m, x=individual_c)) + geom_point(aes(color=run_number, size=time_taken)) + ggtitle("Scatter plot showing run number against time taken and reward")
run_1 %>% filter(individual_c<-3, individual_m <0.0025, individual_m > 0.0015) %>% ggplot(aes(x=as.factor(run_number), y=time_taken)) + geom_violin() + ggtitle("Violin plot of time taken for each simulation run against population number")
ggplot(data=run_1, aes(x=as.factor(run_number), y=reward))+geom_boxplot()+geom_jitter(position=position_jitter(0.2), alpha=0.5, color='blue') + ggtitle("Boxplot and jitter of run number against reward")
run_1 %>% filter(individual_c<-3, individual_m <0.0025, individual_m > 0.0015) %>% ggplot(aes(x=as.factor(run_number), y=reward)) + geom_violin() + ggtitle("Violin plot of reward against population number")
ggplot(run_1, aes(x=reward, y=time_taken))+stat_smooth(method = "lm")+geom_point()+xlab("Absolute Percentage Error") + ggtitle("Scatter plot of time taken for each simulation run against \nabsolute percentage error")
dif_sum = run_1_long %>% inner_join(actual_mix_2018_reduced, by='key') %>% group_by(id,key) %>% mutate(total_difference = value.x-value.y) %>% ddply(.(id, key), summarise, difference_sum = sum(total_difference), time_taken=time_taken, reward=reward, run_number=run_number) %>% group_by(id) %>% summarise(tot_diff = sum(difference_sum), time_taken=mean(time_taken), reward=mean(reward), run_number=mean(run_number))

plot_ly(data=dif_sum, x=~tot_diff, y=~time_taken, z=~reward, type="scatter3d", mode="markers", color=~run_number) 
ggplot()+geom_point(data=run_1, aes(x=time_taken, y=reward, color=run_number)) + ggtitle("Scatter plot of time taken against reward")
run_1

run_1_long = gather(run_1, "key", "value", "coal", "ccgt", "wind", "nuclear", "solar")

# run_1_long %>% inner_join(actual_mix_2018_reduced, by='key') %>% group_by(id,key) %>% mutate(total_difference = value.x-value.y) %>% group_by(id, key) %>% summarise(difference_sum = sum(total_difference))

ggplot(data=dif_sum, aes(x=tot_diff, y=time_taken, color=reward)) + geom_point()+geom_smooth()+xlim(-11000,10000)

# actual_mix_2018_reduced
ggplot(data=dif_sum, aes(x=tot_diff, color=time_taken, y=reward)) + geom_point()+geom_smooth()+xlim(-11000,10000)
ggplot(run_1, aes(x=run_number, y=time_taken, color=reward)) + geom_point() +
   geom_smooth(method='lm')
LS0tCnRpdGxlOiAiUiBOb3RlYm9vayIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKCmBgYHtyfQpsaWJyYXJ5KCJ0aWR5dmVyc2UiKQpsaWJyYXJ5KCJnZ3Bsb3QyIikKbGlicmFyeShwbHlyKQpsaWJyYXJ5KCJtY28iKQpgYGAKYGBge3J9CnJ1bl8xID0gcmVhZF9jc3YoJy9Vc2Vycy9iMTAxNzU3OS9Eb2N1bWVudHMvUGhEL1Byb2plY3RzLzEwLiBFTEVDU0lNL3J1bi92YWxpZGF0aW9uLW9wdGltaXNhdGlvbi9kYXRhL3J1bl8yLmNzdicpCnRhaWwocnVuXzEpCmBgYApgYGB7cn0KZ2dwbG90KGZpbHRlcihydW5fMSksIGFlcyh5PWluZGl2aWR1YWxfbSwgeD1pbmRpdmlkdWFsX2MpKSArIGdlb21faGV4KGJpbnM9MTApCmBgYAoKYGBge3J9CnAgPSBnZ3Bsb3QocnVuXzEsIGFlcyh5PWluZGl2aWR1YWxfbSwgeD1pbmRpdmlkdWFsX2MsIGNvbG9yPXJld2FyZCksIHNpemU9MTApICsgZmFjZXRfd3JhcCh+cnVuX251bWJlcikgKyBnZW9tX3BvaW50KCkgKyBnZ3RpdGxlKCJTY2F0dGVyIHBsb3Qgb2YgaW5wdXQgcGFyYW1ldGVycyBhbmQgcmV3YXJkIikKcCRsYWJlbHMkZmlsbCA8LSAiQWJzb2x1dGUgXG5QZXJjZW50YWdlIFxuRXJyb3IiCnByaW50KHApCmBgYApgYGB7cn0KcCA9IGdncGxvdChydW5fMSwgYWVzKHk9aW5kaXZpZHVhbF9tLCB4PWluZGl2aWR1YWxfYywgY29sb3I9cmV3YXJkKSwgc2l6ZT0xMCkgKyBmYWNldF93cmFwKH5ydW5fbnVtYmVyKSArIGdlb21faml0dGVyKCkgKyBnZ3RpdGxlKCJTY2F0dGVyIChqaXR0ZXIpIHBsb3Qgb2YgaW5wdXQgcGFyYW1ldGVycyBhbmQgcmV3YXJkIikKcCRsYWJlbHMkZmlsbCA8LSAiQWJzb2x1dGUgXG5QZXJjZW50YWdlIFxuRXJyb3IiCnByaW50KHApCmBgYAoKCmBgYHtyfQpwID0gZ2dwbG90KHJ1bl8xLCBhZXMoeT1pbmRpdmlkdWFsX20sIHg9aW5kaXZpZHVhbF9jLCBjb2xvcj10aW1lX3Rha2VuKSwgc2l6ZT0xMCkgKyBmYWNldF93cmFwKH5ydW5fbnVtYmVyKSArIGdlb21faml0dGVyKCkgKyBnZ3RpdGxlKCJTY2F0dGVyIHBsb3Qgb2YgaW5wdXQgcGFyYW1ldGVycyBhbmQgdGltZSB0YWtlbiIpCnAkbGFiZWxzJGZpbGwgPC0gIlRpbWUgXG5UYWtlbiIKcHJpbnQocCkKCmBgYAoKYGBge3J9CnA9Z2dwbG90KHJ1bl8xLCBhZXMoeT1pbmRpdmlkdWFsX20sIHg9aW5kaXZpZHVhbF9jKSkgKyBzdGF0X3N1bW1hcnlfaGV4KGFlcyh6ID0gcmV3YXJkKSwgYmlucz0xMCkgKyBmYWNldF93cmFwKH4gcnVuX251bWJlcikgKyBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gcm91bmQoc2VxKG1pbihydW5fMSRpbmRpdmlkdWFsX2MpLCBtYXgocnVuXzEkaW5kaXZpZHVhbF9jKSwgYnkgPSAyNSksMSkpICsgZ2d0aXRsZSgiSGV4YWdvbmFsIGhlYXRtYXAgb2YgaW5wdXQgcGFyYW1ldGVycyBhbmQgQWJzb2x1dGUgUGVyY2VudGFnZSBFcnJvciIpCnAkbGFiZWxzJGZpbGwgPC0gIkFic29sdXRlIFxuUGVyY2VudGFnZSBcbkVycm9yIgpwcmludChwKQoKZ2dzYXZlKCJ+L0Rlc2t0b3AvZ2VuZXRpY19hbGdvcml0aG1fcHJvZ3Jlc3Npb24ucG5nIikKYGBgCgpgYGB7cn0KcnVuXzEgJT4lIGdyb3VwX2J5KHJ1bl9udW1iZXIpICU+JSBzdW1tYXJpc2UoYXZnX3Jld2FyZCA9IG1lYW4ocmV3YXJkKSkgJT4lIGdncGxvdCgpKyBnZW9tX3Ntb290aChkYXRhPXJ1bl8xLCBhZXMoeD1ydW5fbnVtYmVyLCByZXdhcmQpKStnZW9tX2xpbmUoYWVzKHg9cnVuX251bWJlciwgeT1hdmdfcmV3YXJkKSkrZ2d0aXRsZSgiQXZlcmFnZSByZXdhcmQgdnMgUG9wdWxhdGlvbiBOdW1iZXIiKSAKYGBgCgoKCgoKYGBge3J9CiMgYWNjdXJhdGVfYXJlYSA9IGZpbHRlcihydW5fMSwgaW5kaXZpZHVhbF9jPC00LjgsIGluZGl2aWR1YWxfYz4tNTAsIGluZGl2aWR1YWxfbSA8MC4wMDMsIGluZGl2aWR1YWxfbSA+IDAuMDAyMykKIyBhY2N1cmF0ZV9hcmVhID0gZmlsdGVyKHJ1bl8xLCByZXdhcmQgPCAwLjIpCiMgYWNjdXJhdGVfYXJlYSA9IGZpbHRlcihydW5fMSwgcnVuX251bWJlcj09MTEpCmFjY3VyYXRlX2FyZWEgPSBmaWx0ZXIocnVuXzEsIHJ1bl9udW1iZXIgPT0gMTYpCgphY2N1cmF0ZV9hcmVhCgpgYGAKCmBgYHtyfQogZ2dwbG90KGRhdGE9YWNjdXJhdGVfYXJlYSwgYWVzKHg9aW5kaXZpZHVhbF9jLCB5PWluZGl2aWR1YWxfbSwgY29sb3I9cmV3YXJkKSkrZ2VvbV9qaXR0ZXIoKStnZW9tX3BvaW50KGNvbG9yPSJyZWQiLCBhbHBoYT0wLjUpICsgZ2d0aXRsZSgiU2NhdHRlciBhbmQgSml0dGVycGxvdCBvZiBSZXdhcmQgYWdhaW5zdCBJbnB1dCBQYXJhbWV0ZXJzIGZvciBcbkxhc3QgUG9wdWxhdGlvbiBvZiBHQSIpCgpgYGAKCgpgYGB7cn0KYWNjdXJhdGVfYXJlYV9sb25nID0gZ2F0aGVyKGFjY3VyYXRlX2FyZWEsICJrZXkiLCAidmFsdWUiLCAiY29hbCIsICJjY2d0IiwgIndpbmQiLCAibnVjbGVhciIsICJzb2xhciIpCmFjY3VyYXRlX2FyZWFfbG9uZ19wZXJjID0gYWNjdXJhdGVfYXJlYV9sb25nICU+JSBncm91cF9ieShpZCkgJT4lIG11dGF0ZSh2YWx1ZV9wZXJjID0gdmFsdWUvc3VtKHZhbHVlKSkKCmBgYAoKCmBgYHtyfQpwYXJhbXNfbW9yZV90aGFuXzEwID0gcnVuXzEgJT4lIGdyb3VwX2J5KGluZGl2aWR1YWxfYywgaW5kaXZpZHVhbF9tKSAlPiUgbXV0YXRlKG51bWJlciA9IG4oKSkgJT4lIHVuZ3JvdXAoKSAlPiUgZmlsdGVyKG51bWJlcj4gMTApICU+JSBtdXRhdGVfaWYoaXMubnVtZXJpYywgcm91bmQsIDYpIAoKcGFyYW1zX21vcmVfdGhhbl8xMCAlPiUgZ2dwbG90KGFlcyhhcy5mYWN0b3IoaW5kaXZpZHVhbF9jKSwgcmV3YXJkKSkgK2dlb21fdmlvbGluKCkrZmFjZXRfd3JhcCh+aW5kaXZpZHVhbF9tKSsgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgaGp1c3QgPSAxKSkgK2dlb21faml0dGVyKHBvc2l0aW9uPXBvc2l0aW9uX2ppdHRlcigwLjIpLCBhbHBoYT0wLjA1KSArIGdndGl0bGUoIlZpb2xpbiBwbG90IG9mIGlucHV0IHBhcmFtZXRlcnMgd2l0aCBtb3JlIHRoYW4gMTAgc2ltdWxhdGlvbnMgXG5hZ2FpbnN0IGRpc3RyaWJ1dGlvbiBvZiByZXdhcmQiKQoKYGBgCgpgYGB7cn0KYWNjdXJhdGVfYXJlYSAlPiUgbXV0YXRlX2lmKGlzLm51bWVyaWMsIHJvdW5kLCA2KSAlPiUgZ2dwbG90KGFlcyhhcy5mYWN0b3IoaW5kaXZpZHVhbF9jKSwgcmV3YXJkKSkgK2dlb21fdmlvbGluKCkrZmFjZXRfd3JhcCh+aW5kaXZpZHVhbF9tKSsgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgaGp1c3QgPSAxKSkgK2dlb21faml0dGVyKHBvc2l0aW9uPXBvc2l0aW9uX2ppdHRlcigwLjIpKSsgZ2d0aXRsZSgiVmlvbGluIHBsb3Qgb2YgaW5wdXQgcGFyYW1ldGVycyB3aXRoIG1vcmUgdGhhbiAxMCBzaW11bGF0aW9ucyBvZiBsYXN0IHBvcHVsYXRpb24gb2YgR0FcbmFnYWluc3QgZGlzdHJpYnV0aW9uIG9mIHJld2FyZCIpCgpgYGAKCgpgYGB7cn0KYWN0dWFsX21peCA9IHJlYWRfY3N2KCcvVXNlcnMvYjEwMTc1NzkvRG9jdW1lbnRzL1BoRC9Qcm9qZWN0cy8xMC4gRUxFQ1NJTS9lbGVjc2ltL2RhdGEvcHJvY2Vzc2VkL2VsZWN0cmljaXR5X21peC9lbmVyZ3lfbWl4X2hpc3RvcmljYWwuY3N2JykKYWN0dWFsX21peF8yMDE4ID0gZmlsdGVyKGFjdHVhbF9taXgsIHllYXI9PTIwMTgpCgoKYWN0dWFsX21peF8yMDE4JHR5cGUgPSAiYWN0dWFsIgoKYWN0dWFsX21peF8yMDE4CmBgYAoKYGBge3J9CmFjdHVhbF9taXhfMjAxOF9yZWR1Y2VkID0gZmlsdGVyKGFjdHVhbF9taXhfMjAxOCwgdmFyaWFibGUgJWluJSBjKCJjY2d0IiwgJ3dpbmQnLCAnbnVjbGVhcicsICdzb2xhcicsICdjb2FsJykpCmFjdHVhbF9taXhfMjAxOF9yZWR1Y2VkID0gYWN0dWFsX21peF8yMDE4X3JlZHVjZWQgJT4lIG11dGF0ZSh2YWx1ZV9wZXJjID0gdmFsdWUvc3VtKHZhbHVlKSkKYWN0dWFsX21peF8yMDE4X3JlZHVjZWQgPSBkcGx5cjo6cmVuYW1lKGFjdHVhbF9taXhfMjAxOF9yZWR1Y2VkLCBrZXk9dmFyaWFibGUpCmhlYWQoYWN0dWFsX21peF8yMDE4X3JlZHVjZWQpCmBgYApgYGB7cn0KYWNjdXJhdGVfYXJlYV9sb25nX3BlcmMkdHlwZSA9ICdwcmVkaWN0ZWQnCmFjY3VyYXRlX2FyZWFfbG9uZ19wZXJjCgpjb21wYXJpc29uID0gcmJpbmQoc2VsZWN0KHVuZ3JvdXAoYWNjdXJhdGVfYXJlYV9sb25nX3BlcmMpLCAia2V5IiwgInR5cGUiLCAidmFsdWUiLCAndmFsdWVfcGVyYycpLCBzZWxlY3QoYWN0dWFsX21peF8yMDE4X3JlZHVjZWQsIC1YMSwgLXllYXIpKQpgYGAKCgoKCgpgYGB7cn0KZ2dwbG90KGNvbXBhcmlzb24sIGFlcyh4PWtleSwgeT12YWx1ZV9wZXJjLCBmaWxsPXR5cGUpKSArCiAgc3RhdF9zdW1tYXJ5KGdlb20gPSAiYmFyIiwgZnVuLnkgPSBtZWFuLCBwb3NpdGlvbiA9ICJkb2RnZTIiKSsgc3RhdF9zdW1tYXJ5KGdlb20gPSAiZXJyb3JiYXIiLCBmdW4uZGF0YSA9IG1lYW5fc2UsIHBvc2l0aW9uID0gImRvZGdlMiIpICsgeWxhYigiRW5lcmd5IE1peCAoJSkiKSArIGdndGl0bGUoIkNvbWJpbmF0aW9uIG9mIGFsbCBydW5zIGluIGxhc3QgZ2VuZXRpYyBhbGdvcml0aG0gcnVuIikKCmdnc2F2ZSgnfi9EZXNrdG9wL2F2ZXJhZ2VfZXJyb3Jfb2ZfYmVzdF9wYXJhbXMucG5nJykKYGBgCgpgYGB7cn0KYmVzdF9yZXN1bHQgPSBydW5fMSAlPiUgZ3JvdXBfYnkoImlkIikgJT4lIGZpbHRlcihyZXdhcmQ9PW1pbihyZXdhcmQpKSAlPiUgZ2F0aGVyKCJrZXkiLCAidmFsdWUiLCAiY29hbCIsICJjY2d0IiwgIndpbmQiLCAibnVjbGVhciIsICJzb2xhciIpICU+JSBncm91cF9ieShpZCkgJT4lIG11dGF0ZSh2YWx1ZV9wZXJjID0gdmFsdWUvc3VtKHZhbHVlKSkgJT4lIG11dGF0ZSh0eXBlPSJwcmVkaWN0ZWQiKQoKY29tcGFyaXNvbl9iZXN0ID0gcmJpbmQoc2VsZWN0KHVuZ3JvdXAoYmVzdF9yZXN1bHQpLCAia2V5IiwgInR5cGUiLCAidmFsdWUiLCAndmFsdWVfcGVyYycpLCBzZWxlY3QoYWN0dWFsX21peF8yMDE4X3JlZHVjZWQsIC1YMSwgLXllYXIpKQoKZ2dwbG90KGNvbXBhcmlzb25fYmVzdCwgYWVzKHg9a2V5LCB5PXZhbHVlX3BlcmMsIGZpbGw9dHlwZSkpICsgZ2VvbV9jb2wocG9zaXRpb24gPSAiZG9kZ2UyIikgKyB5bGFiKCJFbmVyZ3kgTWl4ICglKSIpICsgZ2d0aXRsZSgiQmVzdCBzaW5nbGUgcnVuIikKYGBgCgoKYGBge3J9CnBkY19leGFtcGxlID0gcmVhZF9jc3YoJy9Vc2Vycy9iMTAxNzU3OS9Eb2N1bWVudHMvUGhEL1Byb2plY3RzLzEwLiBFTEVDU0lNL25vdGVib29rcy92YWxpZGF0aW9uLW9wdGltaXNhdGlvbi9kYXRhL3ByaWNlX2RlbWFuZF9jdXJ2ZV9leGFtcGxlLmNzdicpCgoKZGVtYW5kX3JhbmdlID0gZGF0YS5mcmFtZSh4PXNlcShmcm9tPW1pbihwZGNfZXhhbXBsZSRzZWdtZW50X2RlbWFuZCksbWF4KHBkY19leGFtcGxlJHNlZ21lbnRfZGVtYW5kKSxsZW5ndGgub3V0PTUwMCkpCgpnZXRfbGluZSA9IGZ1bmN0aW9uKGMsIG0pewogICAgeSA9IG0gKiBkZW1hbmRfcmFuZ2UgKyBjCiAgICByZXR1cm4oeSkKfQoKZ2V0X3ggPSBmdW5jdGlvbigpewogICAgcmV0dXJuKGRlbWFuZF9yYW5nZSkKfQoKIyBsaW5lcyA9IGFjY3VyYXRlX2FyZWEgJT4lIGdyb3VwX2J5KGlkKSAlPiUgYXBwbHkoZ2V0X2xpbmUoLikpCgpsaW5lcyA9IGRkcGx5KGFjY3VyYXRlX2FyZWEsIC4oaWQpLCB0cmFuc2Zvcm0sIHk9Z2V0X2xpbmUoaW5kaXZpZHVhbF9jLCBpbmRpdmlkdWFsX20pLCB4PWdldF94KCkpCgoKZ2dwbG90KCkgKyBnZW9tX2xpbmUoZGF0YT1saW5lcywgYWVzKHg9eC4xLCB5PXgsIGdyb3VwPWlkLCBjb2xvcj1yZXdhcmQpKSArIHN0YXRfc21vb3RoKGRhdGE9cGRjX2V4YW1wbGUsIGFlcyh4PXNlZ21lbnRfZGVtYW5kLCB5PWFjY2VwdGVkX3ByaWNlKSwgbWV0aG9kPSJsbSIsIGNvbD0icmVkIikgKyBnZW9tX3BvaW50KGRhdGE9cGRjX2V4YW1wbGUsIGFlcyh4PXNlZ21lbnRfZGVtYW5kLCB5PWFjY2VwdGVkX3ByaWNlKSkgKyB4bGFiKCJEZW1hbmQgKE1XKSIpICsgeWxhYigiQWNjZXB0ZWQgUHJpY2UiKSArIGdndGl0bGUoIkxhc3QgZ2VuZXRpYyBhbGdvcml0aG0gcnVuIHByaWNlIGN1cnZlcyBiZXR3ZWVuIDIwMjMtMjAyOCBcbmNvbXBhcmVkIHRvIHByaWNlIGluIDIwMTgiKQoKYGBgCgpgYGB7cn0KYmVzdF83NV9wZXJjZW50aWxlID0gYWNjdXJhdGVfYXJlYSAlPiUgZ3JvdXBfYnkoaW5kaXZpZHVhbF9jLCBpbmRpdmlkdWFsX20pICU+JSBzdW1tYXJpc2UobnVtYmVyPW4oKSwgcXVhbnRpbGVzID0gbGlzdChlbmZyYW1lKHF1YW50aWxlKHJld2FyZCwgcHJvYnM9YygwLjI1LDAuNSwwLjc1KSkpKSkgJT4lIHVubmVzdCAlPiUgdW5ncm91cCgpICU+JSBmaWx0ZXIobnVtYmVyPjEwKSAlPiUgZ3JvdXBfYnkobmFtZSkgJT4lZmlsdGVyKHJhbmsodmFsdWUsIHRpZXMubWV0aG9kPSJmaXJzdCIpPT0xKQoKcCA9IGRkcGx5KGJlc3RfNzVfcGVyY2VudGlsZSwgLihuYW1lKSwgdHJhbnNmb3JtLCB5PWdldF9saW5lKGluZGl2aWR1YWxfYywgaW5kaXZpZHVhbF9tKSwgeD1nZXRfeCgpKSAgJT4lIGdncGxvdCgpICsgZ2VvbV9saW5lKGFlcyh4PXguMSwgeT14LCBjb2xvcj1uYW1lKSkrIHN0YXRfc21vb3RoKGRhdGE9cGRjX2V4YW1wbGUsIGFlcyh4PXNlZ21lbnRfZGVtYW5kLCB5PWFjY2VwdGVkX3ByaWNlKSwgbWV0aG9kPSJsbSIsIGNvbD0ieWVsbG93IikgKyBnZW9tX3BvaW50KGRhdGE9cGRjX2V4YW1wbGUsIGFlcyh4PXNlZ21lbnRfZGVtYW5kLCB5PWFjY2VwdGVkX3ByaWNlKSkgKyB4bGFiKCJEZW1hbmQgKE1XKSIpICsgeWxhYigiQWNjZXB0ZWQgUHJpY2UiKSArIGdndGl0bGUoIlByaWNlIGN1cnZlIG9mIGJlc3QgcnVucyBhdCBkaWZmZXJlbnQgcGVyY2VudGlsZXMgb2YgbGFzdCBnZW5ldGljIFxuYWxnb3JpdGhtIHBvcHVsYXRpb24iKQpwJGxhYmVscyRmaWxsIDwtICJQZXJjZW50aWxlcyIKcHJpbnQocCkKYGBgCmBgYHtyfQpiZXN0X3BlcmNlbnRpbGVzX2NvbXBhcmlzb24gPSBpbm5lcl9qb2luKGFjY3VyYXRlX2FyZWEsIGJlc3RfNzVfcGVyY2VudGlsZSwgYnk9YygnaW5kaXZpZHVhbF9jJywgJ2luZGl2aWR1YWxfbScpKSAlPiUgZ2F0aGVyKCJrZXkiLCAidmFsdWUiLCAiY29hbCIsICJjY2d0IiwgIndpbmQiLCAibnVjbGVhciIsICJzb2xhciIpICU+JSBncm91cF9ieShpZCkgJT4lIG11dGF0ZSh2YWx1ZV9wZXJjID0gdmFsdWUvc3VtKHZhbHVlKSkgJT4lIG11dGF0ZSh0eXBlPW5hbWUpCgpjb21wYXJpc29uX3BlcmNlbnRpbGVzID0gcmJpbmQoc2VsZWN0KHVuZ3JvdXAoYmVzdF9wZXJjZW50aWxlc19jb21wYXJpc29uKSwgImtleSIsICJ0eXBlIiwgInZhbHVlIiwgJ3ZhbHVlX3BlcmMnKSwgc2VsZWN0KGFjdHVhbF9taXhfMjAxOF9yZWR1Y2VkLCAtWDEsIC15ZWFyKSkKCmNvbXBhcmlzb25fcGVyY2VudGlsZXMgJT4lIGdncGxvdChhZXMoeD1rZXksIHk9dmFsdWVfcGVyYywgZmlsbD10eXBlKSkgKyBzdGF0X3N1bW1hcnkoZ2VvbSA9ICJiYXIiLCBmdW4ueSA9IG1lYW4sIHBvc2l0aW9uID0gImRvZGdlMiIpKyBzdGF0X3N1bW1hcnkoZ2VvbSA9ICJlcnJvcmJhciIsIGZ1bi5kYXRhID0gbWVhbl9zZSwgcG9zaXRpb24gPSAiZG9kZ2UyIikgKyBnZ3RpdGxlKCJDb21wYXJpc29uIG9mIGVuZXJneSBtaXggb2YgZmluYWwgZ2VuZXRpYyBhbGdvcml0aG0gcG9wdWxhdGlvbiBcbmF0IGRpZmZlcmVudCBxdWFudGlsZSBsZXZlbHMiKQoKCmBgYApgYGB7cn0KcmJpbmQoZmlsdGVyKHNlbGVjdCh1bmdyb3VwKGJlc3RfcGVyY2VudGlsZXNfY29tcGFyaXNvbiksICJrZXkiLCAidHlwZSIsICJ2YWx1ZSIsICd2YWx1ZV9wZXJjJyksdHlwZT09IjUwJSIpLCBzZWxlY3QoYWN0dWFsX21peF8yMDE4X3JlZHVjZWQsIC1YMSwgLXllYXIpKSAlPiUgZ2dwbG90KGFlcyh4PWtleSwgeT12YWx1ZV9wZXJjLCBmaWxsPXR5cGUpKSArIHN0YXRfc3VtbWFyeShnZW9tID0gImJhciIsIGZ1bi55ID0gbWVhbiwgcG9zaXRpb24gPSAiZG9kZ2UyIikrIHN0YXRfc3VtbWFyeShnZW9tID0gImVycm9yYmFyIiwgZnVuLmRhdGEgPSBtZWFuX3NlLCBwb3NpdGlvbiA9ICJkb2RnZTIiKSArIGdndGl0bGUoIkJlc3QgcGFyYW1ldGVyIGNvbWJpbmF0aW9ucyBmcm9tIGZpbmFsIGdlbmV0aWMgYWxnb3JpdGhtIHJ1biIpCgpgYGAKCgojIyBCRVNUIFBBUkFNRVRFUiBDT01CSU5BVElPTiBGUk9NIEFMTCBSVU5TCgpgYGB7cn0KYmVzdF83NV9wZXJjZW50aWxlX2FsbF9ydW5zID0gcnVuXzEgJT4lIGdyb3VwX2J5KGluZGl2aWR1YWxfYywgaW5kaXZpZHVhbF9tKSAlPiUgc3VtbWFyaXNlKG51bWJlcj1uKCksIHF1YW50aWxlcyA9IGxpc3QoZW5mcmFtZShxdWFudGlsZShyZXdhcmQsIHByb2JzPWMoMC4yNSwwLjUsMC43NSwgMC45LCAxLjApKSkpKSAlPiUgdW5uZXN0ICU+JSB1bmdyb3VwKCkgJT4lIGZpbHRlcihudW1iZXI+MTApICU+JSBncm91cF9ieShuYW1lKSAlPiVmaWx0ZXIocmFuayh2YWx1ZSwgdGllcy5tZXRob2Q9ImZpcnN0Iik9PTEpCgpwID0gZGRwbHkoYmVzdF83NV9wZXJjZW50aWxlX2FsbF9ydW5zLCAuKG5hbWUpLCB0cmFuc2Zvcm0sIHk9Z2V0X2xpbmUoaW5kaXZpZHVhbF9jLCBpbmRpdmlkdWFsX20pLCB4PWdldF94KCkpICAlPiUgZ2dwbG90KCkgKyBnZW9tX2xpbmUoYWVzKHg9eC4xLCB5PXgsIGNvbG9yPW5hbWUpKSsgc3RhdF9zbW9vdGgoZGF0YT1wZGNfZXhhbXBsZSwgYWVzKHg9c2VnbWVudF9kZW1hbmQsIHk9YWNjZXB0ZWRfcHJpY2UpLCBtZXRob2Q9ImxtIiwgY29sPSJ5ZWxsb3ciKSArIGdlb21fcG9pbnQoZGF0YT1wZGNfZXhhbXBsZSwgYWVzKHg9c2VnbWVudF9kZW1hbmQsIHk9YWNjZXB0ZWRfcHJpY2UpKSArIHhsYWIoIkRlbWFuZCAoTVcpIikgKyB5bGFiKCJBY2NlcHRlZCBQcmljZSIpICsgZ2d0aXRsZSgiUGVyY2VudGlsZXMgZnJvbSBhbGwgcnVucyIpCnAkbGFiZWxzJGZpbGwgPC0gIlBlcmNlbnRpbGVzIgpwcmludChwKQpgYGAKYGBge3J9CmJlc3RfcGVyY2VudGlsZXNfY29tcGFyaXNvbiA9IGlubmVyX2pvaW4ocnVuXzEsIGJlc3RfNzVfcGVyY2VudGlsZV9hbGxfcnVucywgYnk9YygnaW5kaXZpZHVhbF9jJywgJ2luZGl2aWR1YWxfbScpKSAlPiUgZ2F0aGVyKCJrZXkiLCAidmFsdWUiLCAiY29hbCIsICJjY2d0IiwgIndpbmQiLCAibnVjbGVhciIsICJzb2xhciIpICU+JSBncm91cF9ieShpZCkgJT4lIG11dGF0ZSh2YWx1ZV9wZXJjID0gdmFsdWUvc3VtKHZhbHVlKSkgJT4lIG11dGF0ZSh0eXBlPW5hbWUpCgpjb21wYXJpc29uX3BlcmNlbnRpbGVzID0gcmJpbmQoc2VsZWN0KHVuZ3JvdXAoYmVzdF9wZXJjZW50aWxlc19jb21wYXJpc29uKSwgImtleSIsICJ0eXBlIiwgInZhbHVlIiwgJ3ZhbHVlX3BlcmMnKSwgc2VsZWN0KGFjdHVhbF9taXhfMjAxOF9yZWR1Y2VkLCAtWDEsIC15ZWFyKSkKCmNvbXBhcmlzb25fcGVyY2VudGlsZXMgJT4lIGdncGxvdChhZXMoeD1rZXksIHk9dmFsdWVfcGVyYywgZmlsbD10eXBlKSkgKyBzdGF0X3N1bW1hcnkoZ2VvbSA9ICJiYXIiLCBmdW4ueSA9IG1lYW4sIHBvc2l0aW9uID0gImRvZGdlMiIpKyBzdGF0X3N1bW1hcnkoZ2VvbSA9ICJlcnJvcmJhciIsIGZ1bi5kYXRhID0gbWVhbl9zZSwgcG9zaXRpb24gPSAiZG9kZ2UyIikKYGBgCmBgYHtyfQpyYmluZChmaWx0ZXIoc2VsZWN0KHVuZ3JvdXAoYmVzdF9wZXJjZW50aWxlc19jb21wYXJpc29uKSwgImtleSIsICJ0eXBlIiwgInZhbHVlIiwgJ3ZhbHVlX3BlcmMnKSx0eXBlPT0iNzUlIiB8IHR5cGU9PSI5MCUiKSwgc2VsZWN0KGFjdHVhbF9taXhfMjAxOF9yZWR1Y2VkLCAtWDEsIC15ZWFyKSkgJT4lIGdncGxvdChhZXMoeD1rZXksIHk9dmFsdWVfcGVyYywgZmlsbD10eXBlKSkgKyBzdGF0X3N1bW1hcnkoZ2VvbSA9ICJiYXIiLCBmdW4ueSA9IG1lYW4sIHBvc2l0aW9uID0gImRvZGdlMiIpKyBzdGF0X3N1bW1hcnkoZ2VvbSA9ICJlcnJvcmJhciIsIGZ1bi5kYXRhID0gbWVhbl9zZSwgcG9zaXRpb24gPSAiZG9kZ2UyIikgKyBnZ3RpdGxlKCJCZXN0IHBhcmFtZXRlciBjb21iaW5hdGlvbnMgZnJvbSBhbGwgZ2VuZXRpYyBhbGdvcml0aG0gcnVucyIpCgpgYGAKCiMjIENhbGN1bGF0aW9uIG9mIGJlc3QgcXVhbnRpbGUgdG8gc2VsZWN0IGNvbWJpbmF0aW9uIG9mIHBhcmFtZXRlcnMgZm9yIGxvd2VzdCByZXdhcmQKCmBgYHtyfQpiZXN0X3BhcmFtcyA9IHBhcmFtc19tb3JlX3RoYW5fMTAgJT4lIGdyb3VwX2J5KGluZGl2aWR1YWxfYywgaW5kaXZpZHVhbF9tKSAlPiUgZ2F0aGVyKCJrZXkiLCAidmFsdWUiLCAiY29hbCIsICJjY2d0IiwgIndpbmQiLCAibnVjbGVhciIsICJzb2xhciIpICU+JSBncm91cF9ieShpZCkgJT4lIG11dGF0ZSh2YWx1ZV9wZXJjID0gdmFsdWUvc3VtKHZhbHVlKSkgJT4lIGdyb3VwX2J5KGlkLCBrZXkpICU+JSBpbm5lcl9qb2luKGFjdHVhbF9taXhfMjAxOF9yZWR1Y2VkLCBieT0ia2V5IikgJT4lIG11dGF0ZShkaWZmX3BlcmMgPSBhYnModmFsdWVfcGVyYy54LXZhbHVlX3BlcmMueSkpICU+JSBncm91cF9ieShpZCkgJT4lIHN1bW1hcmlzZShtZWFuX2RpZmZfcGVyYyA9IG1lYW4oZGlmZl9wZXJjKSwgaW5kaXZpZHVhbF9jID0gbWVhbihpbmRpdmlkdWFsX2MpLCBpbmRpdmlkdWFsX20gPSBtZWFuKGluZGl2aWR1YWxfbSkpICU+JSBmaWx0ZXIocmFuayhtZWFuX2RpZmZfcGVyYywgdGllcy5tZXRob2Q9ImZpcnN0Iik9PTEpCgpiZXN0X3BhcmFtc19jb21wYXJpc29uID0gZmlsdGVyKHJ1bl8xLG5lYXIoeD1pbmRpdmlkdWFsX2MsIHk9YmVzdF9wYXJhbXMkaW5kaXZpZHVhbF9jWzFdLCB0b2w9MC4wMDAwMSksbmVhcih4PWluZGl2aWR1YWxfbSwgeT1iZXN0X3BhcmFtcyRpbmRpdmlkdWFsX21bMV0sIHRvbD0wLjAwMDAxKSkgJT4lIGdhdGhlcigia2V5IiwgInZhbHVlIiwgImNvYWwiLCAiY2NndCIsICJ3aW5kIiwgIm51Y2xlYXIiLCAic29sYXIiKSAlPiUgZ3JvdXBfYnkoaWQpICU+JSBtdXRhdGUodmFsdWVfcGVyYyA9IHZhbHVlL3N1bSh2YWx1ZSkpICU+JSBtdXRhdGUodHlwZT0icHJlZGljdGVkIikKCmNvbXBhcmlzb25fYmVzdF9wYXJhbXMgPSByYmluZChzZWxlY3QodW5ncm91cChiZXN0X3BhcmFtc19jb21wYXJpc29uKSwgImtleSIsICJ0eXBlIiwgInZhbHVlIiwgJ3ZhbHVlX3BlcmMnKSwgc2VsZWN0KGFjdHVhbF9taXhfMjAxOF9yZWR1Y2VkLCAtWDEsIC15ZWFyKSkKCmNvbXBhcmlzb25fYmVzdF9wYXJhbXMgJT4lIGdncGxvdChhZXMoeD1rZXksIHk9dmFsdWVfcGVyYywgZmlsbD10eXBlKSkgKyBzdGF0X3N1bW1hcnkoZ2VvbSA9ICJiYXIiLCBmdW4ueSA9IG1lYW4sIHBvc2l0aW9uID0gImRvZGdlMiIpKyBzdGF0X3N1bW1hcnkoZ2VvbSA9ICJlcnJvcmJhciIsIGZ1bi5kYXRhID0gbWVhbl9zZSwgcG9zaXRpb24gPSAiZG9kZ2UyIikgKyBnZ3RpdGxlKCJDb21wYXJpc29uIG9mIHByZWRpY3RlZCB2cyBhY3R1YWwgZm9yIGJlc3QgcGFyYW1ldGVyIHNldCBvdmVyIDEwIGZyb20gYWxsIHJ1bnMiKQoKCgojIyBHZXR0aW5nIE1BUEUgZm9yIGJlc3QgcGFyYW0KCnByZWRpY3RlZF9tZWFuc19iZXN0X3BhcmFtID0gZmlsdGVyKHJ1bl8xLG5lYXIoeD1pbmRpdmlkdWFsX2MsIHk9YmVzdF9wYXJhbXMkaW5kaXZpZHVhbF9jWzFdLCB0b2w9MC4wMDAwMSksbmVhcih4PWluZGl2aWR1YWxfbSwgeT1iZXN0X3BhcmFtcyRpbmRpdmlkdWFsX21bMV0sIHRvbD0wLjAwMDAxKSkgJT4lIGdhdGhlcigia2V5IiwgInZhbHVlIiwgImNvYWwiLCAiY2NndCIsICJ3aW5kIiwgIm51Y2xlYXIiLCAic29sYXIiKSAlPiUgZ3JvdXBfYnkoaWQpICU+JSBtdXRhdGUodmFsdWVfcGVyYyA9IHZhbHVlL3N1bSh2YWx1ZSkpICU+JSBtdXRhdGUodHlwZT0icHJlZGljdGVkIikgJT4lIGdyb3VwX2J5KGluZGl2aWR1YWxfYywgaW5kaXZpZHVhbF9tLCBrZXkpICU+JSBzdW1tYXJpc2UocHJlZGljdGVkX3BlcmMgPSBtZWFuKHZhbHVlX3BlcmMpKSAKCnByaW50KHBhc3RlKCJNQVBFID0gIiwgTUFQRShwcmVkaWN0ZWRfbWVhbnNfYmVzdF9wYXJhbSRwcmVkaWN0ZWRfcGVyYywgYWN0dWFsX21peF8yMDE4X3JlZHVjZWQkdmFsdWVfcGVyYykpKQoKYGBgCgpgYGB7cn0KcCA9IGJlc3RfcGFyYW1zICU+JSB0cmFuc2Zvcm0oeT1nZXRfbGluZShpbmRpdmlkdWFsX2MsIGluZGl2aWR1YWxfbSksIHg9Z2V0X3goKSkgICU+JSBnZ3Bsb3QoKSArIGdlb21fbGluZShhZXMoeD14LjEsIHk9eCkpKyBzdGF0X3Ntb290aChkYXRhPXBkY19leGFtcGxlLCBhZXMoeD1zZWdtZW50X2RlbWFuZCwgeT1hY2NlcHRlZF9wcmljZSksIG1ldGhvZD0ibG0iLCBjb2w9InllbGxvdyIpICsgZ2VvbV9wb2ludChkYXRhPXBkY19leGFtcGxlLCBhZXMoeD1zZWdtZW50X2RlbWFuZCwgeT1hY2NlcHRlZF9wcmljZSkpICsgeGxhYigiRGVtYW5kIChNVykiKSArIHlsYWIoIkFjY2VwdGVkIFByaWNlIikgKyBnZ3RpdGxlKCJCZXN0IHBhcmFtYXRlciBjb21iaW5hdGlvbiBwcmljZSBjdXJ2ZSIpCnAkbGFiZWxzJGZpbGwgPC0gIlBlcmNlbnRpbGVzIgpwcmludChwKQoKYGBgCgoKIyBDYWxjdWxhdGlvbiBvZiBiZXN0IHBhcmFtZXRlcnMgd2l0aCBsb3dlc3QgdmFyaWFuY2UgYW5kIGVycm9yCmBgYHtyfQojIGJlc3RfcGFyYW1zID0gcGFyYW1zX21vcmVfdGhhbl8xMCAlPiUgZ3JvdXBfYnkoaW5kaXZpZHVhbF9jLCBpbmRpdmlkdWFsX20pICU+JSBnYXRoZXIoImtleSIsICJ2YWx1ZSIsICJjb2FsIiwgImNjZ3QiLCAid2luZCIsICJudWNsZWFyIiwgInNvbGFyIikgJT4lIGdyb3VwX2J5KGlkKSAlPiUgbXV0YXRlKHZhbHVlX3BlcmMgPSB2YWx1ZS9zdW0odmFsdWUpKSAlPiUgZ3JvdXBfYnkoaWQsIGtleSkgJT4lIGlubmVyX2pvaW4oYWN0dWFsX21peF8yMDE4X3JlZHVjZWQsIGJ5PSJrZXkiKSAlPiUgbXV0YXRlKGRpZmZfcGVyYyA9IGFicyh2YWx1ZV9wZXJjLngtdmFsdWVfcGVyYy55KSkgJT4lIGdyb3VwX2J5KGlkKSAlPiUgc3VtbWFyaXNlKG1lYW5fZGlmZl9wZXJjID0gbWVhbihkaWZmX3BlcmMpLCBpbmRpdmlkdWFsX2MgPSBtZWFuKGluZGl2aWR1YWxfYyksIGluZGl2aWR1YWxfbSA9IG1lYW4oaW5kaXZpZHVhbF9tKSkgJT4lIGZpbHRlcihyYW5rKG1lYW5fZGlmZl9wZXJjLCB0aWVzLm1ldGhvZD0iZmlyc3QiKT09MSkKCiMgY2FsY3VsYXRlX21lYW5fZXJyb3IgPSBmdW5jdGlvbihkZil7CiMgICAgIAojICAgICByZXR1cm4oZ2F0aGVyKGRmLCAia2V5IiwgInZhbHVlIiwgImNvYWwiLCAiY2NndCIsICJ3aW5kIiwgIm51Y2xlYXIiLCAic29sYXIiKSAlPiUgaW5uZXJfam9pbihhY3R1YWxfbWl4XzIwMThfcmVkdWNlZCwgYnk9J2tleScpKQojIH0KCiMgcGFyYW1zX21vcmVfdGhhbl8xMCAlPiUgZ3JvdXBfYnkoaW5kaXZpZHVhbF9jLCBpbmRpdmlkdWFsX20pICU+JSBkbyguLCBjYWxjdWxhdGVfbWVhbl9lcnJvcikKIyBkZHBseShwYXJhbXNfbW9yZV90aGFuXzEwLCAuKGluZGl2aWR1YWxfYywgaW5kaXZpZHVhbF9tKSwgY2FsY3VsYXRlX21lYW5fZXJyb3IpCgpwYXJhbXNfc2RfbWVhbl9yZXdhcmQgPSBwYXJhbXNfbW9yZV90aGFuXzEwICU+JSBncm91cF9ieShpbmRpdmlkdWFsX2MsIGluZGl2aWR1YWxfbSkgJT4lIGdhdGhlcigia2V5IiwgInZhbHVlIiwgImNvYWwiLCAiY2NndCIsICJ3aW5kIiwgIm51Y2xlYXIiLCAic29sYXIiKSAlPiUgZ3JvdXBfYnkoaWQpICU+JSBtdXRhdGUodmFsdWVfcGVyYyA9IHZhbHVlL3N1bSh2YWx1ZSkpICU+JSBncm91cF9ieShpZCwga2V5KSAlPiUgaW5uZXJfam9pbihhY3R1YWxfbWl4XzIwMThfcmVkdWNlZCwgYnk9ImtleSIpICU+JSBtdXRhdGUoZGlmZl9wZXJjID0gYWJzKHZhbHVlX3BlcmMueC12YWx1ZV9wZXJjLnkpKSAlPiUgZ3JvdXBfYnkoaWQpICU+JSBzdW1tYXJpc2UoZGlmZl9zZCA9IHNkKGRpZmZfcGVyYyksIG1lYW5fZGlmZl9wZXJjID0gbWVhbihkaWZmX3BlcmMpLCBpbmRpdmlkdWFsX2MgPSBtZWFuKGluZGl2aWR1YWxfYyksIGluZGl2aWR1YWxfbSA9IG1lYW4oaW5kaXZpZHVhbF9tKSkgJT4lIGdyb3VwX2J5KGluZGl2aWR1YWxfYywgaW5kaXZpZHVhbF9tKSAlPiUgc3VtbWFyaXNlKGRpZmZfc2QgPSBzZChkaWZmX3NkKSwgbWVhbl9kaWZmX3BlcmMgPSBtZWFuKG1lYW5fZGlmZl9wZXJjKSkKCnBhcmFtc19zZF9tZWFuX3Jld2FyZF9vcmRlcmVkID0gYXJyYW5nZShwYXJhbXNfc2RfbWVhbl9yZXdhcmQsIGRpZmZfc2QsIG1lYW5fZGlmZl9wZXJjKQpmcm9udGllciA9IGdldF9mcm9udGllcihhcy5kYXRhLmZyYW1lKHBhcmFtc19zZF9tZWFuX3Jld2FyZF9vcmRlcmVkKSwgZGlmZl9zZCwgbWVhbl9kaWZmX3BlcmMsIGRlY3JlYXNpbmcgPSBGQUxTRSwgcXVhZHJhbnQgPSAiYm90dG9tLmxlZnQiKQoKcGFyYW1zX3NkX21lYW5fcmV3YXJkICU+JSBnZ3Bsb3QoKSArIGdlb21fcG9pbnQoYWVzKGRpZmZfc2QsIG1lYW5fZGlmZl9wZXJjKSkgKyBnZW9tX2xpbmUoZGF0YT1mcm9udGllciwgYWVzKGRpZmZfc2QsIG1lYW5fZGlmZl9wZXJjKSwgY29sb3I9InJlZCIpICsgZ2d0aXRsZSgiUGFyZXRvIGZyb250aWVyIG9mIHN0YW5kYXJkIGRldmlhdGlvbiB2ZXJzdXMgbWVhbiBmb3Igc2ltdWxhdGlvbnMgXG5vZiBtb3JlIHRoYW4gMTAgcnVucyIpCgpgYGAKCmBgYHtyfQpwYXJhbXNfbW9yZV90aGFuXzEwX3BlcmMgPSBwYXJhbXNfbW9yZV90aGFuXzEwICU+JSBncm91cF9ieShpbmRpdmlkdWFsX2MsIGluZGl2aWR1YWxfbSkgJT4lIGdhdGhlcigia2V5IiwgInZhbHVlIiwgImNvYWwiLCAiY2NndCIsICJ3aW5kIiwgIm51Y2xlYXIiLCAic29sYXIiKSAlPiUgZ3JvdXBfYnkoaWQpICU+JSBtdXRhdGUodmFsdWVfcGVyYyA9IHZhbHVlL3N1bSh2YWx1ZSkpCgpmcm9udGllcl9mdWxsX2RhdGEgPSBmcm9udGllciAlPiUgaW5uZXJfam9pbihwYXJhbXNfc2RfbWVhbl9yZXdhcmRfb3JkZXJlZCwgYnk9YygnZGlmZl9zZCcsICdtZWFuX2RpZmZfcGVyYycpKSAlPiUgaW5uZXJfam9pbihwYXJhbXNfbW9yZV90aGFuXzEwX3BlcmMsIGJ5PWMoImluZGl2aWR1YWxfYyIsICJpbmRpdmlkdWFsX20iKSkgJT4lIG11dGF0ZSh0eXBlPXBhc3RlKG1lYW5fZGlmZl9wZXJjLGRpZmZfc2QpKQoKCnJiaW5kKGZpbHRlcihzZWxlY3QoZnJvbnRpZXJfZnVsbF9kYXRhLCAia2V5IiwgInR5cGUiLCAidmFsdWUiLCAndmFsdWVfcGVyYycpKSwgc2VsZWN0KGFjdHVhbF9taXhfMjAxOF9yZWR1Y2VkLCAtWDEsIC15ZWFyKSkgJT4lIGdncGxvdChhZXMoeD1rZXksIHk9dmFsdWVfcGVyYywgZmlsbD10eXBlKSkgKyBzdGF0X3N1bW1hcnkoZ2VvbSA9ICJiYXIiLCBmdW4ueSA9IG1lYW4sIHBvc2l0aW9uID0gImRvZGdlMiIpKyBzdGF0X3N1bW1hcnkoZ2VvbSA9ICJlcnJvcmJhciIsIGZ1bi5kYXRhID0gbWVhbl9zZSwgcG9zaXRpb24gPSAiZG9kZ2UyIikgKyBnZ3RpdGxlKCJCZXN0IHBhcmFtZXRlciBjb21iaW5hdGlvbnMgZnJvbSBhbGwgZ2VuZXRpYyBhbGdvcml0aG0gcnVucyIpCiAgICAKYGBgCgpgYGB7cn0KYmVzdF9vbmUgPSBzZWxlY3QoZmlsdGVyKGZyb250aWVyX2Z1bGxfZGF0YSwgbWVhbl9kaWZmX3BlcmM8MC4wNjcpLCAia2V5IiwgInR5cGUiLCAidmFsdWUiLCAndmFsdWVfcGVyYycpCmJlc3Rfb25lJHR5cGUgPSAiU2ltdWxhdGVkIgoKYWN0dWFsX21peF8yMDE4X3JlZHVjZWQkdHlwZSA9ICJBY3R1YWwiCgpyYmluZChiZXN0X29uZSwgc2VsZWN0KGFjdHVhbF9taXhfMjAxOF9yZWR1Y2VkLCAtWDEsIC15ZWFyKSkgJT4lIGdncGxvdChhZXMoeD1rZXksIHk9dmFsdWVfcGVyYywgZmlsbD10eXBlKSkgKyBzdGF0X3N1bW1hcnkoZ2VvbSA9ICJiYXIiLCBmdW4ueSA9IG1lYW4sIHBvc2l0aW9uID0gImRvZGdlMiIpKyBzdGF0X3N1bW1hcnkoZ2VvbSA9ICJlcnJvcmJhciIsIGZ1bi5kYXRhID0gbWVhbl9zZSwgcG9zaXRpb24gPSAiZG9kZ2UyIikgKyB0aGVtZV9jbGFzc2ljKCkgK3RoZW1lKHRleHQgPSBlbGVtZW50X3RleHQoc2l6ZT0xOCkpKyB4bGFiKCIiKSArIHlsYWIoIkVsZWN0cmljaXR5IE1peCAoJSkiKSArc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZSA9ICdTZXQyJykKCmdnc2F2ZSgifi9Eb2N1bWVudHMvUGhEL1Byb2plY3RzLzEwLiBFTEVDU0lNL25vdGVib29rcy92YWxpZGF0aW9uLW9wdGltaXNhdGlvbi9maWd1cmVzL2ludHJvZHVjdGlvbi9iZXN0X3J1bi5wZGYiKQpgYGAKCmBgYHtyfQpiZXN0X2FjdHVhbCA9IHJiaW5kKGJlc3Rfb25lLCBzZWxlY3QoYWN0dWFsX21peF8yMDE4X3JlZHVjZWQsIC1YMSwgLXllYXIpKQpiZXN0X2FjdHVhbF9zdGF0cyA9IGJlc3RfYWN0dWFsICU+JSBncm91cF9ieSh0eXBlLCBrZXkpICU+JSBzdW1tYXJpc2Uoc2RfdmFsdWUgPSBzZCh2YWx1ZV9wZXJjKSwgbWVhbl92YWx1ZSA9IG1lYW4odmFsdWVfcGVyYykpIAoKYWN0dWFscyA9IGZpbHRlcihiZXN0X2FjdHVhbF9zdGF0cywgdHlwZT09IkFjdHVhbCIpJG1lYW5fdmFsdWUKcHJlZGljdGVkID0gZmlsdGVyKGJlc3RfYWN0dWFsX3N0YXRzLCB0eXBlPT0iU2ltdWxhdGVkIikkbWVhbl92YWx1ZQoKUk1TRShhY3R1YWxzLHByZWRpY3RlZCkKYGBgCgpgYGB7cn0KcD1nZ3Bsb3QocnVuXzEsIGFlcyh5PWluZGl2aWR1YWxfbSwgeD1pbmRpdmlkdWFsX2MpKSArIHN0YXRfc3VtbWFyeV9oZXgoYWVzKHogPSB0aW1lX3Rha2VuKSwgYmlucz0xMCkgKyBmYWNldF93cmFwKH4gcnVuX251bWJlcikgKyBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gcm91bmQoc2VxKG1pbihydW5fMSRpbmRpdmlkdWFsX2MpLCBtYXgocnVuXzEkaW5kaXZpZHVhbF9jKSwgYnkgPSAyNSksMSkpICsgZ2d0aXRsZSgiSGV4YWdvbmFsIGhlYXRtYXAgb2YgaW5wdXQgcGFyYW1ldGVycyBhZ2FpbnN0IHRpbWUgdGFrZW4gd2l0aCByZXNwZWN0IHRvIHBvcHVsYXRpb24gbnVtYmVyIikKcCRsYWJlbHMkZmlsbCA8LSAiVGltZSBcbiBUYWtlbiIKcHJpbnQocCkKCmdnc2F2ZSgnfi9EZXNrdG9wL3RpbWUtdGFrZW4tcGFyYW1ldGVycy5wbmcnKQpgYGAKCmBgYHtyfQpydW5fMSAlPiUgYXJyYW5nZShkZXNjKHJ1bl9udW1iZXIpKSAlPiUgZ2dwbG90KGFscGhhPTAuMSwgYWVzKHk9aW5kaXZpZHVhbF9tLCB4PWluZGl2aWR1YWxfYykpICsgZ2VvbV9wb2ludChhZXMoY29sb3I9cnVuX251bWJlciwgc2l6ZT10aW1lX3Rha2VuKSkgKyBnZ3RpdGxlKCJTY2F0dGVyIHBsb3Qgc2hvd2luZyBydW4gbnVtYmVyIGFnYWluc3QgdGltZSB0YWtlbiBhbmQgcmV3YXJkIikKYGBgCgpgYGB7cn0KcnVuXzEgJT4lIGZpbHRlcihpbmRpdmlkdWFsX2M8LTMsIGluZGl2aWR1YWxfbSA8MC4wMDI1LCBpbmRpdmlkdWFsX20gPiAwLjAwMTUpICU+JSBnZ3Bsb3QoYWVzKHg9YXMuZmFjdG9yKHJ1bl9udW1iZXIpLCB5PXRpbWVfdGFrZW4pKSArIGdlb21fdmlvbGluKCkgKyBnZ3RpdGxlKCJWaW9saW4gcGxvdCBvZiB0aW1lIHRha2VuIGZvciBlYWNoIHNpbXVsYXRpb24gcnVuIGFnYWluc3QgcG9wdWxhdGlvbiBudW1iZXIiKQpgYGAKYGBge3J9CmdncGxvdChkYXRhPXJ1bl8xLCBhZXMoeD1hcy5mYWN0b3IocnVuX251bWJlciksIHk9cmV3YXJkKSkrZ2VvbV9ib3hwbG90KCkrZ2VvbV9qaXR0ZXIocG9zaXRpb249cG9zaXRpb25faml0dGVyKDAuMiksIGFscGhhPTAuNSwgY29sb3I9J2JsdWUnKSArIGdndGl0bGUoIkJveHBsb3QgYW5kIGppdHRlciBvZiBydW4gbnVtYmVyIGFnYWluc3QgcmV3YXJkIikKYGBgCgoKYGBge3J9CnJ1bl8xICU+JSBmaWx0ZXIoaW5kaXZpZHVhbF9jPC0zLCBpbmRpdmlkdWFsX20gPDAuMDAyNSwgaW5kaXZpZHVhbF9tID4gMC4wMDE1KSAlPiUgZ2dwbG90KGFlcyh4PWFzLmZhY3RvcihydW5fbnVtYmVyKSwgeT1yZXdhcmQpKSArIGdlb21fdmlvbGluKCkgKyBnZ3RpdGxlKCJWaW9saW4gcGxvdCBvZiByZXdhcmQgYWdhaW5zdCBwb3B1bGF0aW9uIG51bWJlciIpCgpgYGAKCmBgYHtyfQpnZ3Bsb3QocnVuXzEsIGFlcyh4PXJld2FyZCwgeT10aW1lX3Rha2VuKSkrc3RhdF9zbW9vdGgobWV0aG9kID0gImxtIikrZ2VvbV9wb2ludCgpK3hsYWIoIkFic29sdXRlIFBlcmNlbnRhZ2UgRXJyb3IiKSArIGdndGl0bGUoIlNjYXR0ZXIgcGxvdCBvZiB0aW1lIHRha2VuIGZvciBlYWNoIHNpbXVsYXRpb24gcnVuIGFnYWluc3QgXG5hYnNvbHV0ZSBwZXJjZW50YWdlIGVycm9yIikKCmBgYAoKYGBge3J9CmRpZl9zdW0gPSBydW5fMV9sb25nICU+JSBpbm5lcl9qb2luKGFjdHVhbF9taXhfMjAxOF9yZWR1Y2VkLCBieT0na2V5JykgJT4lIGdyb3VwX2J5KGlkLGtleSkgJT4lIG11dGF0ZSh0b3RhbF9kaWZmZXJlbmNlID0gdmFsdWUueC12YWx1ZS55KSAlPiUgZGRwbHkoLihpZCwga2V5KSwgc3VtbWFyaXNlLCBkaWZmZXJlbmNlX3N1bSA9IHN1bSh0b3RhbF9kaWZmZXJlbmNlKSwgdGltZV90YWtlbj10aW1lX3Rha2VuLCByZXdhcmQ9cmV3YXJkLCBydW5fbnVtYmVyPXJ1bl9udW1iZXIpICU+JSBncm91cF9ieShpZCkgJT4lIHN1bW1hcmlzZSh0b3RfZGlmZiA9IHN1bShkaWZmZXJlbmNlX3N1bSksIHRpbWVfdGFrZW49bWVhbih0aW1lX3Rha2VuKSwgcmV3YXJkPW1lYW4ocmV3YXJkKSwgcnVuX251bWJlcj1tZWFuKHJ1bl9udW1iZXIpKQoKcGxvdF9seShkYXRhPWRpZl9zdW0sIHg9fnRvdF9kaWZmLCB5PX50aW1lX3Rha2VuLCB6PX5yZXdhcmQsIHR5cGU9InNjYXR0ZXIzZCIsIG1vZGU9Im1hcmtlcnMiLCBjb2xvcj1+cnVuX251bWJlcikgCmBgYAoKYGBge3J9CmdncGxvdCgpK2dlb21fcG9pbnQoZGF0YT1ydW5fMSwgYWVzKHg9dGltZV90YWtlbiwgeT1yZXdhcmQsIGNvbG9yPXJ1bl9udW1iZXIpKSArIGdndGl0bGUoIlNjYXR0ZXIgcGxvdCBvZiB0aW1lIHRha2VuIGFnYWluc3QgcmV3YXJkIikKYGBgCgpgYGB7cn0KcnVuXzEKCnJ1bl8xX2xvbmcgPSBnYXRoZXIocnVuXzEsICJrZXkiLCAidmFsdWUiLCAiY29hbCIsICJjY2d0IiwgIndpbmQiLCAibnVjbGVhciIsICJzb2xhciIpCgojIHJ1bl8xX2xvbmcgJT4lIGlubmVyX2pvaW4oYWN0dWFsX21peF8yMDE4X3JlZHVjZWQsIGJ5PSdrZXknKSAlPiUgZ3JvdXBfYnkoaWQsa2V5KSAlPiUgbXV0YXRlKHRvdGFsX2RpZmZlcmVuY2UgPSB2YWx1ZS54LXZhbHVlLnkpICU+JSBncm91cF9ieShpZCwga2V5KSAlPiUgc3VtbWFyaXNlKGRpZmZlcmVuY2Vfc3VtID0gc3VtKHRvdGFsX2RpZmZlcmVuY2UpKQoKZ2dwbG90KGRhdGE9ZGlmX3N1bSwgYWVzKHg9dG90X2RpZmYsIHk9dGltZV90YWtlbiwgY29sb3I9cmV3YXJkKSkgKyBnZW9tX3BvaW50KCkrZ2VvbV9zbW9vdGgoKSt4bGltKC0xMTAwMCwxMDAwMCkKCiMgYWN0dWFsX21peF8yMDE4X3JlZHVjZWQKYGBgCmBgYHtyfQpnZ3Bsb3QoZGF0YT1kaWZfc3VtLCBhZXMoeD10b3RfZGlmZiwgY29sb3I9dGltZV90YWtlbiwgeT1yZXdhcmQpKSArIGdlb21fcG9pbnQoKStnZW9tX3Ntb290aCgpK3hsaW0oLTExMDAwLDEwMDAwKQpgYGAKCmBgYHtyfQpnZ3Bsb3QocnVuXzEsIGFlcyh4PXJ1bl9udW1iZXIsIHk9dGltZV90YWtlbiwgY29sb3I9cmV3YXJkKSkgKyBnZW9tX3BvaW50KCkgKwogICBnZW9tX3Ntb290aChtZXRob2Q9J2xtJykKYGBg